Skip to content

Compiler#137

Merged
bfarmer67 merged 44 commits into
developfrom
compiler
Mar 9, 2026
Merged

Compiler#137
bfarmer67 merged 44 commits into
developfrom
compiler

Conversation

@bfarmer67
Copy link
Copy Markdown
Contributor

Description

This PR introduces Hyperbee.Expressions.Compiler (HEC) — a high-performance, IR-based compiler for .NET
expression trees — and wires it into the existing Hyperbee.Expressions async/enumerable state machine
infrastructure via a pluggable interface.

Motivation

The System expression compiler (Expression.Compile()) is a general-purpose interpreter. It is correct
and portable, but it is slow. For applications that compile expression trees at runtime — rule engines,
query builders, dynamic pipelines, scripted workflows — compilation cost dominates. FastExpressionCompiler
(FEC) is a well-known improvement, but it has correctness gaps on complex patterns (nullable lifted ops,
certain try/catch shapes, RuntimeVariables, Dynamic expressions) and is a third-party dependency.

Hyperbee.Expressions already generates async and enumerable state machines as expression trees. Those
state machines need to be compiled. Before this PR, the only options were SEC (slow) or FEC (fast but
incomplete). Neither integrates cleanly with the state machine generator — each compiled MoveNext body
had to be compiled by whichever compiler happened to receive the outer lambda, which broke encapsulation.

Goals:

  1. A first-party, high-performance compiler — close to FEC speed, full correctness, no third-party runtime dependency.
  2. Pluggable compilationAsyncBlockExpression should not be coupled to any specific compiler.
    The outer compiler (SEC, FEC, or HEC) should be able to compile inner state machine bodies automatically.
  3. DI-friendly abstractionIExpressionCompiler for injectable compiler selection.
  4. Full expression coverage — every ExpressionType the System compiler handles, plus the patterns FEC
    does not.

What's New

Hyperbee.Expressions.Compiler (new package)

A four-stage IR-based compiler:

LambdaExpression
→ ExpressionLowerer (expression tree → flat IROp instruction stream)
→ StackSpillPass (eliminate merge-point locals at branch phi-points)
→ PeepholePass (constant folding, branch simplification, load/store elimination)
→ DeadCodePass (remove unreachable instructions after unconditional branches)
→ ILEmissionPass (IR → CIL → DynamicMethod)

Public API surface:

Type Description
HyperbeeCompiler Static façade — Compile, TryCompile, CompileWithFallback, CompileToMethod, CompileToInstanceMethod, UseAsDefault
HyperbeeExpressionCompiler IExpressionCompiler singleton wrapper for DI
HyperbeeCompilerExtensions lambda.CompileHyperbee() extension method
CompilerDiagnostics IRCapture callback for IR listing inspection

Hyperbee.Expressions — pluggable coroutine builder (M1–M3)

Three milestones refactored the state machine generator to support compiler plug-in without changing
expression construction semantics:

Milestone Change
M1 ICoroutineDelegateBuilder interface; HyperbeeCoroutineDelegateBuilder; CoroutineBuilderContext (AsyncLocal ambient + static default)
M2 HEC compiles all BlockAsync patterns correctly; fixes for short-branch overflow, struct field method calls, and extension-node constant scanning
M3 ExpressionRuntimeOptions.DelegateBuilder removed; IExpressionCompiler + SystemExpressionCompiler added; CoroutineBuilderContext.SetScope; HyperbeeExpressionCompiler; ambient set automatically during HyperbeeCompiler.Compile()

The net effect: BlockAsync options describe behavior only (Optimize, ModuleBuilderProvider, ExpressionCapture).
Compiler choice flows through ambient context — no options need to change when switching compilers.


Performance

Benchmarks run on .NET 9, BenchmarkDotNet, 3 iterations + 3 warmup, Intel Core i9-9980HK.

Compilation Speed

Tier System FEC HEC HEC vs System HEC vs FEC
Simple 28.7 µs 3.1 µs 3.6 µs 8× faster 1.16×
Closure 27.4 µs 2.9 µs 3.4 µs 8× faster 1.17×
TryCatch 50.4 µs 3.9 µs 5.1 µs 10× faster 1.31×
Complex 136.7 µs 3.4 µs 4.4 µs 31× faster 1.29×
Loop 67.9 µs 4.2 µs 6.4 µs 11× faster 1.51×
Switch 60.4 µs 3.4 µs 5.2 µs 12× faster 1.53×

HEC compiles 9–34× faster than the System compiler and within 1.16–1.54× of FEC.

Memory Allocations (per compile call)

Tier System FEC HEC vs System
Simple 4,335 B 904 B 2,152 B 50% fewer
Closure 4,279 B 895 B 2,136 B 50% fewer
TryCatch 5,893 B 1,519 B 3,999 B 32% fewer
Complex 4,741 B 1,390 B 2,512 B 47% fewer
Loop 6,710 B 1,110 B 4,264 B 36% fewer
Switch 6,264 B 1,352 B 4,128 B 34% fewer

HEC allocates up to 50% less than the System compiler.

Execution Speed

Delegates produced by HEC execute at the same speed as SEC and FEC. At sub-nanosecond scale, differences
are within measurement noise. For CPU-bound workloads (Complex tier, ~25 ns), all three compilers are
within 10% of each other.

Performance Implementation Notes

  • The dominant cost difference vs FEC is the constants-array closure: HEC captures non-embeddable
    constants (object refs, delegates, nested lambdas) in a object[] closure and indexes into it at
    runtime. FEC inlines constants directly via ldobj/stobj.
  • AsyncLocal writes are expensive (~1 µs each). CoroutineBuilderContext.Exchange() is gated on
    ScanForNonEmbeddableConstants — for simple expressions (no extension nodes), the ambient write is
    skipped entirely, recovering ~2 µs per compile call for the common case.
  • Three IL optimization passes (StackSpill, Peephole, DeadCode) reduce emitted instruction count and
    eliminate unnecessary locals, bringing HEC within striking distance of FEC's tight output.

Expression Coverage

HEC handles every ExpressionType the System compiler handles, plus patterns FEC does not:

Category Notes
Arithmetic, bitwise, shift All signed/unsigned checked variants
Comparison Ceq, Clt, Cgt, un-variants
Conversion Convert, ConvertChecked, ConvertCheckedUn, Box, UnboxAny, CastClass, IsInst
Nullable lifted ops Full three-value-logic lifted arithmetic, bitwise, and comparison
Member access Instance/static fields, properties, indexers
Method calls Static, virtual, interface, constrained (value-type virtual calls)
Arrays NewArray, LoadElement, StoreElement, LoadArrayLength, LoadElementAddress
Control flow IfThen, IfThenElse, Switch, Loop, Goto, Label, Return
Exception handling TryCatch, TryCatchFinally, TryFinally, TryFault, ExceptionFilter
Closures Captured variables via StrongBox<T> hoisting; nested lambdas
RuntimeVariables IRuntimeVariables via RuntimeVariablesHelper
Dynamic DynamicExpression via CallSite
Quote Expression.Quote for nested expression trees
CompileToMethod Static MethodBuilder target (no closure support — all constants must be embeddable)
CompileToInstanceMethod Instance/value-type MethodBuilder target (for IAsyncStateMachine.MoveNext)

Test Suite

2,610 test instances across 32 expression test files, 7 BlockAsync integration suites, and a
FEC issue regression suite. All tests pass (2,589 passed, 21 skipped).

Suite Tests Notes
Expression tests (32 files) ~2,340 3 compilers × all expression types
BlockAsync integration (7 files) ~210 Basic, conditional, core, loop, switch, try/catch, context
FEC known issues 10 Documents FEC failures; suppressed in main test runs
CompileToMethod / CompileToInstanceMethod ~40 Static and instance MethodBuilder patterns

Test policy:

  • System fails → invalid pattern; test removed
  • FEC fails → suppressed with Assert.Inconclusive + documented in FecKnownIssues.cs
  • HEC fails → fix required (no suppressions for HEC)

Bugs Fixed in Hyperbee.Expressions

Several bugs were discovered during M2 integration testing and fixed in the core library:

Fix Description
Short-branch overflow ILEmissionPass now auto-upgrades short (br.s) branches to long (br) when the offset exceeds ±127 bytes. Required for the ConditionalAwait multi-state pattern.
Struct field method calls ExpressionLowerer emits Constrained prefix + LoadFieldAddress for value-type field method calls (e.g., TaskAwaiter<T>.GetResult()). Fixes stack underflow in multi-state SequentialAwaits and TryCatch MoveNext patterns.
Extension-node constant scanning ScanForNonEmbeddableConstants returns true for ExpressionType.Extension so AsyncBlockExpression reduction is handled correctly during HEC compilation.
Void-assign dup elimination ExpressionLowerer.LowerBlock correctly suppresses the Dup emitted by LowerAssign when the assignment result is immediately discarded.
ldelema for struct-element field assignment LoadElementAddress (ldelema) is now emitted instead of LoadElement + StoreElement when the target is a struct field element address.
Merge-point local elimination StackSpillPass eliminates the temporary StoreLocal/LoadLocal pair inserted at branch merge-points when the value is immediately consumed, reducing unnecessary locals in if/else and switch patterns.

Documentation

Full just-the-docs documentation added under docs/ matching the structure of sibling projects
(hyperbee.pipeline, hyperbee.json, hyperbee.xs):

docs/
├── index.md Landing page
├── expressions/ 12 expression type pages
├── configuration/ Runtime options, module providers, DI
├── compiler/ Overview, API reference, diagnostics, performance
└── lab/ Fetch, JSON, Map/Reduce

Files Changed

  • 37 commits, 117 files, +36,567 / −363 lines
  • src/Hyperbee.Expressions.Compiler/ — new package (19 source files)
  • src/Hyperbee.Expressions/ — 8 files modified (interfaces, ambient context, options)
  • test/Hyperbee.Expressions.Compiler.Tests/ — new project (32 test files, ~2,340 test instances)
  • test/Hyperbee.Expressions.Compiler.IssueTests/ — new project (FEC regression suite)
  • test/Hyperbee.Expressions.Compiler.Benchmarks/ — new project (compilation + execution benchmarks)
  • docs/ — 27 markdown pages + config

Checklist

  • All 669 Hyperbee.Expressions.Tests pass (no regressions)
  • All 2,589 Hyperbee.Expressions.Compiler.Tests pass (21 skipped — documented FEC limitations)
  • All 10 Hyperbee.Expressions.Compiler.IssueTests pass
  • Benchmarks measured and documented
  • Documentation complete

bfarmer67 added 30 commits March 1, 2026 16:37
…b API

Adds the new Hyperbee.ExpressionCompiler class library (net8.0;net9.0;net10.0)
with HyperbeeCompiler static API (Compile, TryCompile, CompileWithFallback) and
HyperbeeCompilerExtensions. All methods are stubs; TryCompile returns null so
CompileWithFallback falls back to Expression.Compile(). Updates solution file.
Add the foundational IR types for the Hyperbee expression compiler:
- IROp enum with all opcodes for Phase 1+ compilation
- IRInstruction readonly struct for cache-friendly instruction storage
- IRBuilder with instruction stream, operand table, locals, and labels
- LocalInfo and LabelInfo readonly record structs for metadata
Single-pass expression tree visitor that lowers to flat IR instructions.
Supports constants, parameters, binary/unary ops, method calls,
conditionals (ternary + IfThen), member access, new objects, blocks,
assignment, default, type conversions, short-circuit logic, and
operator overloads. Unsupported types throw NotSupportedException.
1:1 mapping from IR opcodes to CIL via ILGenerator. Handles all Phase 1
operations including arithmetic, checked variants, comparisons, conversions,
method calls, branching, fields, boxing/unboxing, and non-embeddable
constants loaded from a closure object[] array. Uses short-form opcodes
for locals 0-3 and args 0-3.
…ests

Complete the compiler pipeline: expression tree pre-scan for non-embeddable
constants, lowering to IR, IL emission via DynamicMethod, and delegate
creation. TryCompile now returns a working delegate for all Phase 1
patterns. All 81 compiler tests and 6 issue regression tests pass across
net8.0, net9.0, and net10.0.
…/throw/goto/label

- Add Leave IROp for exiting try/catch blocks
- Implement TryCatch/Throw/Rethrow/Goto/Label lowering in ExpressionLowerer
- Add StackSpillPass to convert Branch to Leave at exception boundaries
- Update ILEmissionPass with BeginTry/BeginCatch/BeginFinally/EndTryCatch/Throw/Rethrow/Leave emission
- Wire StackSpillPass into HyperbeeCompiler pipeline between lowering and emission
- Extend NeedsConstantsArray scanner for TryExpression/GotoExpression/LabelExpression
- Add 62 exception handling tests (basic try/catch, typed catch, catch variable,
  try/catch/finally, throw/rethrow, goto/label, void try, nested try, value results)
- Add 4 HyperbeeCompiler.Compile native tests for Pattern 1 and Pattern 2 in IssueTests
- All 143 Compiler.Tests + 10 IssueTests pass on net8.0/net9.0/net10.0
LowerToIR, RunPasses, EmitDelegate each own a single responsibility.
Removes stale rationale comments and dead NeedsConstantsArray wrapper.
…nd nested lambdas

Implement support for Expression.Lambda and Expression.Invoke with
captured variables using the StrongBox<T> pattern:

- Add CaptureScanner to detect variables captured by nested lambdas
- Extend ExpressionLowerer with Lambda/Invoke handling and StrongBox
  access for captured variables (load via LoadField, store via StoreField)
- Rewrite inner lambdas to accept StrongBox<T> parameters and compile
  with System compiler, enabling shared mutable state between outer
  and inner delegates
- Update HyperbeeCompiler to integrate capture scanning before lowering
- Add ScanForNonEmbeddableConstants support for Lambda/Invoke nodes
- Add ClosureTests with 12 test cases covering no-capture, single
  capture, multiple captures, mutable captures, and mixed scenarios
- Add native HyperbeeCompiler.Compile tests for Pattern 3 in
  FecKnownIssues (no longer needs CompileWithFallback)

All 167 compiler tests + 12 issue tests pass across net8.0/net9.0/net10.0.
…ction init, coalesce, quote

Add support for all remaining expression types to make HyperbeeCompiler
a drop-in replacement for Expression.Compile():

Lowering (ExpressionLowerer):
- Loop expressions with break/continue labels
- Switch expressions with int/string cases, multiple test values, custom comparison
- NewArrayInit and NewArrayBounds (single and multi-dimensional)
- ArrayIndex (binary), ArrayLength (unary), Index (indexer property or array)
- ListInit (List, Dictionary collection initializers)
- MemberInit (property and field assignment bindings)
- Coalesce (null coalescing for reference types and nullable value types)
- TypeEqual (exact type match via GetType() == typeof(T))
- Quote (expression tree as data)
- Power (Math.Pow)
- Unbox (unbox.any)
- DebugInfo (no-op)

IL emission (ILEmissionPass):
- NewArray (newarr), LoadElement/StoreElement (typed ldelem/stelem variants)
- LoadArrayLength (ldlen + conv.i4), LoadAddress (ldloca)

Also fixes void conditional handling in full ternary path to pop non-void
results when the conditional type is void.

60 new tests across 6 test files, all passing on net8.0/net9.0/net10.0.
…eduction, benchmark updates

Add PeepholePass with 7 peephole patterns: StoreLocal/LoadLocal to Dup/StoreLocal,
dead load elimination (LoadConst/Pop, LoadNull/Pop, LoadLocal/Pop), identity Box/UnboxAny
removal, Dup/Pop elimination, and branch-to-fallthrough removal.

Reduce allocations: pre-size IRBuilder lists and ExpressionLowerer dictionaries,
optimize BuildConstantsMapping from O(operands*instructions) to O(operands+instructions)
using a HashSet pre-scan.

Update benchmarks to use HyperbeeCompiler.Compile directly (no fallback needed) and
add Loop and Switch tier benchmarks. Results show Hyperbee within 1.3-1.8x of FEC
compilation speed and 8-30x faster than System compiler.
… bugs

CompilerType.Hyperbee was using CompileWithFallback which silently fell
back to the System compiler on any failure, masking all bugs. Changed to
use HyperbeeCompiler.Compile directly.

Fixed lowering bugs exposed by removing fallback:
- LowerDefault: removed broken InitObj+StoreLocal pattern, use zero-initialized local
- LowerConditional: non-void ternary stores branch results to temp local
- LowerAndAlso/OrElse: replaced Dup across labels with result-local pattern
- LowerCoalesce: rewrote both nullable and reference paths with temp locals
- Added ExpressionType.Increment/Decrement to unary handler
- NewExpression without constructor now handles value-type default
- LowerAssign now supports IndexExpression (array/indexer assignment)
- IRValidator: moved LoadArrayLength from push to neutral category

Also includes allocation reduction (lazy dictionaries, reduced capacities,
StackSpillPass fast-exit), nullable warning fixes, new Phase 9 tests
(559 total), and FEC IssueTests patterns 4-10.
…ment tests

- Pattern 8 (box/unbox in conditional) now uses HyperbeeCompiler.Compile
  natively after LowerConditional fix
- LowerSwitch: non-void switch uses result-local pattern so stack is
  empty at labels, fixing string switch without explicit comparison
- Added PostIncrementAssign, PreIncrementAssign, PostDecrementAssign tests
- Added string switch without explicit comparison test
…timeVariables

- Add lifted nullable binary operations (arithmetic + comparisons) via
  LowerLiftedBinary, LowerLiftedComparison, LowerLiftedArithmetic
- Add lifted nullable unary operations via LowerLiftedUnary
- Add Nullable<T> ↔ T conversions and enum underlying-type conversions
  in LowerConvert (fixes TypeConversionTests for Hyperbee compiler)
- Add exception filter support: BeginFilter / BeginFilteredCatch IR ops,
  IL emission, DeadCodePass sentinel, IRValidator stack depth
- Add RuntimeVariables support: RuntimeVariablesHelper, CaptureScanner
  FindRuntimeVariablesCaptures, LowerRuntimeVariables with StrongBox array
- Fix TypeIs to use ldtoken + Type.GetTypeFromHandle (embeddable in CompileToMethod)
- Add OnesComplement to supported unary ops
- Improve closure binding: BuildClosureBinder for non-invoked captured lambdas
- Remove obsolete IROp entries: LoadClosureVar, StoreClosureVar, CreateDelegate
- Add README.md with architecture overview and benchmark comparison table
- Add FecKnownIssues patterns 11-20 (AddAssign, TypeAs, closures, filter, etc.)
- Add test files: BitwiseTests, CompileToMethodTests, DynamicExpressionTests,
  RuntimeVariablesTests (679 tests passing across net8/net9/net10)
FEC generates incorrect IL for Not(Nullable<bool>): calling the compiled
delegate with any argument causes AccessViolationException (crashes the
test host process). The bug exists because FEC does not null-guard the
lifted Not operation — it attempts to dereference the nullable value
without checking HasValue first.

- NullableTests.Not_NullableBool(Fast): guard before delegate invocation
  with Assert.Fail, documenting the bug rather than crashing the host
- FecKnownIssues.Pattern21: add native Hyperbee verification test with
  explanation of why the FEC variant cannot be safely tested

Effect: 808 tests total, 807 pass, 1 documented FEC failure (no crash).
Previously: 648 tests appeared to pass, then host crashed (160 tests
never ran due to the fatal AccessViolationException mid-run).
…t, IR fixes

Compiler fixes:
- Add unsigned checked IR opcodes (AddCheckedUn, SubCheckedUn, MulCheckedUn, ConvertCheckedUn)
  to IROp, IRValidator, ILEmissionPass, and ExpressionLowerer
- Fix float/double comparisons to use IsUnsignedOrFloat (clt.un/cgt.un for unordered NaN semantics)
- Fix nullable Power lowering to emit null-propagation guard
- Fix ~30 additional expression lowering correctness bugs

Test infrastructure:
- Remove FEC→System fallback from CompileFast — FEC failures now surface as test failures
- FecKnownIssues.cs rewritten: only FEC-specific _FecBug and CompileWithFallback tests
  (no redundant _HyperbeeNative tests); added Pattern23 (ulong LessThan) and Pattern25
  (ConvertChecked ulong→long) as verifiable _FecBug tests
- Policy: System fails → remove test; FEC fails → suppress + FecKnownIssues link; Hyperbee fails → fix
- Remove two invalid all-compiler-crash patterns (NewArrayInit_NullableIntArray,
  ConvertChecked_NullableIntToByte_Overflow)

New test files (6):
- BlockTests.cs, ControlFlowTests.cs, ConvertCheckedTests.cs
- LambdaTests.cs, NullableArithmeticTests.cs, NullableBitwiseTests.cs

Expanded test files (11):
- ArrayTests, BinaryTests, CollectionInitTests, ComparisonTests, ConditionalTests
- ExceptionHandlingTests, LoopTests, NullableTests, SwitchTests, TypeConversionTests, UnaryTests

Result: 1,559 total test instances — 1,551 passed, 8 skipped (all FEC-only), 0 failed
…ompiler bugs

Expand HyperbeeCompiler test suite from ~1,766 to 2,426 instances (0 failures):

Compiler fixes:
- Add RightShiftUn opcode (shr.un) for unsigned logical right shift; uint/ulong RightShift
  now correctly emits shr.un instead of sign-extending shr
- Fix IRValidator false positive: track expected stack depth per label from branch sites
  so conditional sub-expressions inside arithmetic no longer fail stack-depth validation
- Add lifted IsFalse/IsTrue support in LowerLiftedUnary for bool? operands

New test coverage:
- ComparisonTests: NaN comparisons (all 6 ops × float/double), infinity comparisons,
  float basic comparisons, decimal comparisons (+20 methods)
- ArrayTests: 2D/3D array bounds creation, 2D read/write, zero-length arrays,
  nullable array, bool/long/string arrays, out-of-bounds throws (+11 methods)
- BoundaryValueTests: float NaN propagation, infinity arithmetic, int boundary wrapping,
  float/double divide by zero (+10 methods)
- UnaryTests: fix IsFalse/IsTrue nullable semantics (bool? → bool?), fix Negate_SByte
  (widening pattern), FEC suppress for PostIncrementAssign_Double (+fixes)
- ExceptionHandlingTests: fix 3 tests with try/catch type mismatch (void vs value)
- Multiple type-complete arithmetic/comparison tests across session
… and README

- Fix AccessViolationException in LowerConstant: Constant(null, typeof(int?))
  was emitting ldnull, but stelem for a value type expects a struct on the stack.
  CLR zeroes locals on declaration, so a fresh temp local already holds
  default(Nullable<T>) — emit LoadLocal directly instead of LoadNull.
- Re-enable NewArrayInit_NullableIntArray_AccessElements test (3 DataRows).
- Update README compilation benchmarks to latest run (9–34x vs System,
  1.11–1.47x vs FEC); add execution benchmarks table; document CompileToMethod API.
…cStateMachineBuilder

Reverts premature integration of HEC into hyperbee.expressions before the compiler
was proven correct on state-machine patterns. MoveNextCompiler option removed from
ExpressionRuntimeOptions; compiler project reference removed from expressions test project.
…ests

- CompileToInstanceMethodTests: covers field read/write, non-embeddable constant
  rejection, and the StateMachineCompiler sentinel
- StateMachinePatternTests: 12 tests proving HEC compiles every pattern produced
  by the async state machine lowerer — Switch dispatch tables, instance field
  read/write, IfThen+Return (suspend pattern), ref parameter calls, TryCatch
  wrapping, and multi-state MoveNext shapes — all verified against System compiler
Introduce ICoroutineDelegateBuilder and ICoroutineImplementationBuilder
as the abstraction layer for async state machine compilation, replacing
the previous IEntryPointGenerator / IStateMachineGenerator names.

"Coroutine" is the CS term for the suspend/resume pattern underlying
both async/await and yield-return — not tied to state machines and
safe for future .NET runtime-native async (e.g. .NET 11+).

Changes:
- ICoroutineBuilder.cs: defines ICoroutineDelegateBuilder (public) and
  ICoroutineImplementationBuilder (internal, reserved for M3)
- AsyncStateMachineBuilder.cs: DefaultCoroutineDelegateBuilder replaces
  SystemEntryPointGenerator; raw-lambda embedding preserved for default,
  Constant(delegate) used for custom builders
- ExpressionRuntimeOptions.cs: EntryPointGenerator → DelegateBuilder
- HyperbeeCoroutineDelegateBuilder.cs: HEC implementation (was HecEntryPointGenerator)
- CompilerDiagnostics.cs + IRFormatter.cs: diagnostic infrastructure
- nestedCompiler wired in ExpressionLowerer for recursive HEC compilation
- BlockAsyncHecTests.cs: integration tests (3 pass, 8 known HEC bugs)

All Hyperbee.Expressions.Tests pass (669/3). No regressions.
…thodBuilderBox

Replaces the misleadingly named AsyncInterpreterTaskBuilder<TResult> with
AsyncTaskMethodBuilderBox<TResult>, which clearly communicates its purpose:
a typed heap box around the AsyncTaskMethodBuilder<TResult> struct, required
because expression tree MemberExpression access copies struct fields, making
direct struct mutation impossible without a class wrapper.

Adds XML doc comments explaining the struct-copy problem, the box pattern,
and why StrongBox<T> alone would not suffice.
…est suite 2547/21

Compiler fixes:
- HyperbeeCompiler: ScanForNonEmbeddableConstants now returns true for extension
  nodes so AsyncBlockExpression reduction is handled correctly (SingleAwait, VoidResult)
- ILEmissionPass: short branch auto-upgraded to long branch when offset exceeds ±127
  bytes (fixes ConditionalAwait illegal one-byte branch)
- ExpressionLowerer: struct field method calls emit Constrained prefix + LoadFieldAddress
  for TaskAwaiter<T> fields (fixes stack underflow in SequentialAwaits/TryCatch)

Test reorganization:
- BlockAsync*Tests moved from Expressions/ to Integration/ — these are pipeline
  integration tests, not HEC expression pattern tests
- StateMachinePatternTests deleted; patterns redistributed:
  - Ref/out parameter tests added to MethodCallTests (AwaitOnCompleted pattern)
  - Switch dispatch-table tests added to SwitchTests (state-dispatch pattern)

Test expansion:
- MethodCallTests: +5 ref/out parameter tests
- SwitchTests: +3 switch-as-dispatch-table tests (null default, Goto-bodied cases)
- CompileToInstanceMethodTests: 7 → 17 tests
- RuntimeVariablesTests: 5 → 13 tests

Results: 2547 passed, 21 skipped, 0 failed (net9.0)
…ssionCompiler, remove DelegateBuilder from options

- Add CoroutineBuilderContext (AsyncLocal per-compilation + static process-wide default)
  with Exchange(), SetScope(), SetDefault() — compiler choice never passes through BlockAsync options
- Add IExpressionCompiler interface + SystemExpressionCompiler in Hyperbee.Expressions
- Add HyperbeeExpressionCompiler (DI-injectable adapter) in Hyperbee.Expressions.Compiler
- HyperbeeCompiler.Compile() sets ambient via Exchange() in try/finally; adds UseAsDefault()/ClearDefault()
- AsyncStateMachineBuilder reads CoroutineBuilderContext.Current directly; removes DefaultCoroutineDelegateBuilder
- Remove ExpressionRuntimeOptions.DelegateBuilder — options describe behavior only, not compiler choice
- Simplify AsyncBlockExpression.Reduce(); remove ResolvedOptions()
- Integration tests: remove HecOptions() from all BlockAsync tests; IRCapture test uses SetScope()
- Add BlockAsyncContextTests covering ambient and process-wide default paths
…-Inconclusive test

- Add FecKnownIssues.Pattern28: Return(label, Assign(...)) inside async-lowered TryCatch
  generates invalid IL in FEC (incomplete error 1007 detection; FEC issue #495)
- Update BlockAsyncTryCatchTests: standardize Fast suppress message to reference Pattern28
- Remove CompileFast_ShouldReturnNull_ForReturnGotoFromTryCatchWithAssign from
  CompilerCompatibilityTests — always-Inconclusive placeholder, now documented in FecKnownIssues
- Solution skips: 23 total (2 Expressions.Tests + 21 Compiler.Tests), all FEC suppressions
…, fix void-assign and ldelema bugs

Remove unnecessary merge-point result locals from conditional, logical, and coalesce
lowering by leaving values on the evaluation stack at merge points (valid in CIL when
stack depth is consistent across all paths). Saves 3–5 instructions and 1–2 locals per
occurrence.

Void-lambda with bare Assign body: set _discardResult=true to suppress the Dup that
was incorrectly leaving a value on the stack before Ret.

Void block ending in Assign: extend suppressAssign logic so the final Pop is not
emitted after an Assign (the value was never pushed in the first place).

ldelema fix: Assign to a field of a struct stored in an array element requires
ldelema (managed pointer) not ldelem (value copy). Add IROp.LoadElementAddress,
emit Ldelema in ILEmissionPass, handle in IRValidator/IRFormatter. New
EmitInstanceForFieldAssign helper routes value-type instances through EmitLoadAddress,
which now handles ArrayIndex-on-value-type via ldelema. Fix also covers mutating
instance method calls on struct array elements.

Add test coverage: void-lambda assign patterns (5 tests), struct-array-element field
and property assignment patterns (5 tests) — all passing for System, FEC, and HEC.
…k delta columns

- Remove IROp.Switch (never emitted), IRValidator.ValidateAlways (never called)
- Remove scope subsystem: IROp.BeginScope/EndScope, IRBuilder.EnterScope/ExitScope,
  LocalInfo.ScopeDepth — entire subsystem was no-op (no IL emitted, no pass consumed it)
- Remove CompilerDiagnostics.ILCapture (declared but never wired or invoked)
- Gate CoroutineBuilderContext.Exchange on ScanForNonEmbeddableConstants to avoid
  unnecessary AsyncLocal writes for simple expressions; pass pre-computed result into
  LowerToIR to eliminate redundant traversal — restores 9-34x speedup vs SEC
- Add BenchmarkExpressions (shared definitions), BenchmarkColumns (RatioToColumn for
  vs-SEC and vs-FEC delta columns), expand ExecutionBenchmarks to all 6 tiers
- Update README with current benchmark numbers and expanded execution table
Add full documentation for Hyperbee.Expressions, Hyperbee.Expressions.Compiler,
and Hyperbee.Expressions.Lab following the established just-the-docs pattern used
across sibling projects (hyperbee.pipeline, hyperbee.json, hyperbee.xs).

Structure (27 markdown files across 4 sections):
- index.md — landing page with package table, quick start, expression type index
- expressions/ — parent + 12 children (async-block, enumerable-block, await, yield,
  for, foreach, while, using, debug, string-format, inject, configuration-value)
- configuration/ — parent + 3 children (runtime-options, module-providers,
  dependency-injection)
- compiler/ — parent + 4 children (overview, api, diagnostics, performance)
- lab/ — parent + 3 children (fetch, json, map-reduce)

Infrastructure:
- _config.yml — adds baseurl, url, footer_content, nav_external_links
- _includes/nav_footer_custom.html — branded footer
- docs.projitems — updated to include all 27 pages
Replace em/en dashes, arrows, box-drawing, micro sign, and other
non-ASCII Unicode with ASCII-only substitutions so all docs .md
files are plain ASCII.
@bfarmer67 bfarmer67 merged commit 2dba944 into develop Mar 9, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant